/*
 * $Id: CharacterSequenceBuilder.java 115 2013-01-16 07:33:46Z vladimirow@mail.ru $
 * $HeadURL: http://ultralog.googlecode.com/svn/ultralog/trunk/src/java/com/google/code/ultralog/utils/CharacterSequenceBuilder.java $
 *
 * This file is a part of ultralog project (http://code.google.com/p/ultralog/).
 * Released under New BSD License.
 */
package com.google.code.ultralog.utils;


/**
 * Implements both {@link CharacterOutputStream} and {@link CharacterSequence}
 * interfaces and allows building character sequences by calling methods of
 * character output stream.
 *
 * @author Mikhail Vladimirov
 */
public class CharacterSequenceBuilder
    implements CharacterOutputStream, CharacterSequence
{
    private char [] buffer;
    private int length = 0;

    /**
     * Create new character sequence builder with initial capacity 16.
     */
    public CharacterSequenceBuilder ()
    {
        this (16);
    }

    /**
     * Create new character sequence builder with given initial capacity.
     *
     * @param initialCapacity initial capacity
     */
    public CharacterSequenceBuilder (int initialCapacity)
    {
        if (initialCapacity < 0)
            throw new IllegalArgumentException (
                "Initial capacity (" + initialCapacity + ") < 0");

        buffer = new char [initialCapacity];
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int getLength ()
    {
        return length;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public char getCharacterAt (int index)
    {
        if (index < 0)
            throw new IllegalArgumentException ("Index (" + index + ") < 0");

        if (index >= length)
            throw new IllegalArgumentException (
                "Index (" + index + ") >= length (" + length + ")");

        return buffer [index];
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void copyCharacters (int offset, int length, char[] destination,
        int destinationOffset)
    {
        if (offset < 0)
            throw new IllegalArgumentException ("Offset (" + offset + ") < 0");

        if (length < 0)
            throw new IllegalArgumentException ("Length (" + length + ") < 0");

        if (destination == null)
            throw new IllegalArgumentException ("Destination is null");

        if (destinationOffset < 0)
            throw new IllegalArgumentException (
                "Destination offset (" + destinationOffset + ") < 0");

        if (offset + length > this.length)
            throw new IllegalArgumentException (
                "Offset (" + offset + ") + length (" + length +
                ") > character sequence length (" + this.length + ")");

        int destinationLength = destination.length;
        if (destinationOffset + length > destinationLength)
            throw new IllegalArgumentException (
                "Destination offset (" + destinationOffset + ") + length (" +
                length + ") > destination length (" + destinationLength + ")");

        System.arraycopy (
            buffer, offset, destination, destinationOffset, length);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void writeCharactersTo (int offset, int length,
        CharacterOutputStream destination)
    {
        if (offset < 0)
            throw new IllegalArgumentException ("Offset (" + offset + ") < 0");

        if (length < 0)
            throw new IllegalArgumentException ("Length (" + length + ") < 0");

        if (destination == null)
            throw new IllegalArgumentException ("Destination is null");

        if (offset + length > this.length)
            throw new IllegalArgumentException (
                "Offset (" + offset + ") + length (" + length +
                ") > character sequence length (" + this.length + ")");

        destination.write (buffer, offset, length);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write (char ch)
    {
        ensureCapacity (length + 1);

        buffer [length++] = ch;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write (char ch1, char ch2)
    {
        ensureCapacity (length + 2);

        buffer [length++] = ch1;
        buffer [length++] = ch2;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write (char ch1, char ch2, char ch3)
    {
        ensureCapacity (length + 3);

        buffer [length++] = ch1;
        buffer [length++] = ch2;
        buffer [length++] = ch3;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write (char ch1, char ch2, char ch3, char ch4)
    {
        ensureCapacity (length + 4);

        buffer [length++] = ch1;
        buffer [length++] = ch2;
        buffer [length++] = ch3;
        buffer [length++] = ch4;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write (char[] buffer, int offset, int length)
    {
        if (buffer == null)
            throw new IllegalArgumentException ("Buffer is null");

        if (offset < 0)
            throw new IllegalArgumentException ("Offset (" + offset + ") < 0");

        if (length < 0)
            throw new IllegalArgumentException ("Length (" + length + ") < 0");

        int bufferLength = buffer.length;
        if (offset + length > bufferLength)
            throw new IllegalArgumentException (
                "Offset (" + offset + ") + length (" + length +
                ") > buffer length (" + bufferLength + ")");

        ensureCapacity (this.length + length);
        System.arraycopy (buffer, offset, this.buffer, this.length, length);
        this.length += length;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write (String string, int offset, int length)
    {
        if (string == null)
            throw new IllegalArgumentException ("String is null");

        if (offset < 0)
            throw new IllegalArgumentException ("Offset (" + offset + ") < 0");

        if (length < 0)
            throw new IllegalArgumentException ("Length (" + length + ") < 0");

        int stringLength = string.length ();
        if (offset + length > stringLength)
            throw new IllegalArgumentException (
                "Offset (" + offset + ") + length (" + length +
                ") > string length (" + stringLength + ")");

        ensureCapacity (this.length + length);
        string.getChars (offset, offset + length, buffer, this.length);
        this.length += length;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write (CharSequence sequence, int offset, int length)
    {
        if (sequence == null)
            throw new IllegalArgumentException ("Sequence is null");

        if (offset < 0)
            throw new IllegalArgumentException ("Offset (" + offset + ") < 0");

        if (length < 0)
            throw new IllegalArgumentException ("Length (" + length + ") < 0");

        int sequenceLength = sequence.length ();
        if (offset + length > sequenceLength)
            throw new IllegalArgumentException (
                "Offset (" + offset + ") + length (" + length +
                ") > sequence length (" + sequenceLength + ")");

        ensureCapacity (this.length + length);
        for (int end = offset + length, i = offset, j = this.length;
             i < end; i++, j++)
            buffer [j] = sequence.charAt (i);
        this.length += length;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void write (CharacterSequence sequence, int offset, int length)
    {
        if (sequence == null)
            throw new IllegalArgumentException ("Sequence is null");

        if (offset < 0)
            throw new IllegalArgumentException ("Offset (" + offset + ") < 0");

        if (length < 0)
            throw new IllegalArgumentException ("Length (" + length + ") < 0");

        int sequenceLength = sequence.getLength ();
        if (offset + length > sequenceLength)
            throw new IllegalArgumentException (
                "Offset (" + offset + ") + length (" + length +
                ") > sequence length (" + sequenceLength + ")");

        ensureCapacity (this.length + length);
        sequence.copyCharacters (offset, length, buffer, this.length);
        this.length += length;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void flush ()
    {
        // Do nothing
    }

    /**
     * Reset the builder.
     */
    public void reset ()
    {
        length = 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString ()
    {
        return new String (buffer, 0, length);
    }

    private void ensureCapacity (int capacity)
    {
        if (capacity > buffer.length)
        {
            char [] newBuffer = new char [capacity * 3 / 2];
            System.arraycopy (buffer, 0, newBuffer, 0, length);
            buffer = newBuffer;
        }
    }
}
